import {
  isNumber,
  isBoolean,
  isString,
  isArray,
  isObject,
  isEmpty,
  isFunction,
  debounce,
} from "lodash";
/**
 * emptyFn 空函数
 */
const emptyFn = () => {};
/**
 * not 高阶函数：对参数执行结果取反
 * @param {function} fn 任何返回布尔类型的函数
 * @returns {function}
 */
const not =
  (fn) =>
  (...args) =>
    !fn(...args);
/**
 * notFn
 */
const {
  notNumber,
  notBoolean,
  notString,
  notArray,
  notObject,
  notEmpty,
  notFunction,
} = Object.entries({
  isNumber,
  isBoolean,
  isString,
  isArray,
  isObject,
  isEmpty,
  isFunction,
}).reduce(
  (acc, [k, v]) => Object.assign(acc, { [k.replace("is", "not")]: not(v) }),
  {}
);

const stringify = (obj) =>
  Object.entries(obj)
    .map(
      ([key, value]) =>
        `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
    )
    .join("&");
const toArray = (v) => {
  if (isObject(v)) return Object.values(v);
  return Array.from(v);
};

const events = Symbol("events");
/**
 * Observable 实现简单的观察者模式
 * 对事件的支持
 */
class Observable {
  constructor() {
    // private property
    this[events] = {};
    // alias
    this.on = this.addListener;
    this.off = this.removeEvent;
    this.emit = this.fireEvent;
  }
  /**
   * removeEvent 移除已经注册过的事件观察函数
   *
   * @param {string} name 事件名
   * @param {function} fn 曾经注册过的事件观察函数
   * @returns void
   */
  removeEvent(name, fn) {
    if (isEmpty(this[events][name])) {
      return this;
    }
    if (fn === "*") {
      delete this[events][name];
    } else {
      const listeners = this[events][name];
      for (let [i, listener] of listeners.entries()) {
        if (listener.fn == fn) {
          listeners.splice(i, 1);
          break;
        }
      }
    }
    return this;
  }
  /**
   * fireEvent * 触发事件
   *
   * @param {string} name 事件名
   * @param {array} args 参数
   * @returns void
   */
  async fireEvent(name, ...args) {
    if (notEmpty(this[events][name])) {
      return Promise.all(
        this[events][name].map((listener) => {
          return new Promise((resolve, reject) => {
            try {
              listener.fn.call(listener.scope, ...args);
              resolve();
            } catch (e) {
              reject(e);
            }
          });
        })
      );
    }
    return this;
  }
  /**
   * addListener 注册事情观察者函数
   *
   * @param {string} name 注册事件名
   * @param {function} fn 响应事件的回调函数
   * @param {object} scope 回调函数的this
   * @returns void
   */
  addListener(name, fn, scope) {
    if (!this[events][name]) {
      this[events][name] = [];
    }
    this[events][name].push({
      fn: fn,
      scope: scope || this,
    });
    return this;
  }
}

// 统一进行防抖
const wrapDebounce = (methods, wait = 1000) =>
  Object.entries(methods).reduce(
    (p, [k, v]) =>
      Object.assign(p, { [k]: debounce(v, wait, { leading: true }) }),
    {}
  );

export {
  Observable,
  emptyFn,
  not,
  stringify,
  toArray,
  isNumber,
  isBoolean,
  isString,
  isArray,
  isObject,
  isEmpty,
  isFunction,
  notNumber,
  notBoolean,
  notString,
  notArray,
  notObject,
  notEmpty,
  notFunction,
  wrapDebounce,
};
